#pragma once
#include <iostream>
#include <functional>
#include <unordered_map>
#include "Sock.hpp"
#include "Log.hpp"
#include "Error.hpp"
#include "Epoll.hpp"
#include "Util.hpp"
#include "Protocol.hpp"
#include "circularQueue.hpp"

/*
    3.2 vs 3.3
    3.3：
        1.将所有的异常处理归类为读写异常去处理
        2.业务处理不同，不再是边解析边获取数据，而是一次读取完所有的数据后，再进行数据处理4
        并且业务处理，不暴露内部的信息，只让上层返回响应即可，自己在内部进行序列化，等行为
        3.不同点还在于，一次处理完所有的请求后，我不再设置读写开关，大部分情况下对方的缓冲区
        是就绪的，那么我直接发送，没有发送完的再交给epoll帮我发送。


*/
class Connection;
class EpollServer;
// 使用协议的命名空间
using namespace ns_Protocol;
using func_t = std::function<Response(Request)>;
/*

*/

using callback = std::function<void(Connection *)>;
static const uint16_t defaultport = 8888;
static const int gnums = 1024;

class Connection
{
public:
    Connection(int fd, int events, std::string ip, uint16_t port) : _fd(fd),
                                                                    _events(events),
                                                                    _ip(ip),
                                                                    _port(port)
    {
    }

    ~Connection()
    {
    }

    void Register(callback recver, callback sender, callback excepter)
    {
        _recver = recver;
        _sender = sender;
        _excepter = excepter;
    }

public:
    // IO信息
    int _fd;             // sock
    uint32_t _events;    // 事件
    std::string _inbuf;  // 输入缓冲区
    std::string _outbuf; // 输出缓冲区
    // IO处理函数
    callback _recver;   // 读事件
    callback _sender;   // 写事件
    callback _excepter; // 异常事件

    // 用户信息
    std::string _ip;
    uint16_t _port;
    // 回指指针
    // 在业务处理时，使用到
    EpollServer *_thisEpoll;
};

class EpollServer
{
public:
    EpollServer(func_t func, circularQueue<Connection *> *cq, uint16_t port = defaultport) : _func(func), _port(port), _cq(cq)
    {
        // 创建epoll模型
        _epoll.Create();
    }
    ~EpollServer()
    {
        _listensock.Close();
        _epoll.Close();
    }
    void InitServer()
    {
        // 1.建立tcp通信
        // 1.1 创建套接字
        _listensock.Socket();
        // 1.2 绑定端口
        _listensock.Bind(_port);
        // 1.3 监听
        _listensock.Listen();
        _listensock.ReuseAddr();//设置端口复用

        // 生产者消费者中，生产者才会调用InitServer()
        // 消费者只有自己的epoll模型，所以不能在这里进行创建epoll模型！

        // // 2.创建epoll模型
        // _epoll.Create();

        // 3.将listensock加入connections中
        // 顺便也把要关心得事件加入到epoll模型中
        // 将epoll更改为ET模式
        // 创建连接
        Connection *conn = CreateConnection(_listensock.Fd(), EPOLLIN | EPOLLET);
        // 绑定方法
        BindProducer(conn);
        // 加入自身的epoll
        AddConnection(conn);

        logMessage(Info, "epollServer init success..");
    }
    // 事件执行器 -- 消费者专属
    void Executer()
    {
        // 消费者，从任务队列中捞取sock，并加入到自己的connection集合内部，进行事件关心
        int timeout = 1000;
        while (true)
        {
            // 1.从任务队列内部捞取Connection
            Connection *conn = nullptr;
            _cq->pop(&conn);
            // 2. 将这个sock 填充自己的connection集合内部，让自己的epoll关心
            // 锁改为非阻塞后，我们需要conn的合法性进行判断
            if (conn)
            {
                logMessage(Info, "%d---消费者# 我从任务队列中获取到新连接sock %d,conn %p,我的epoll模型fd-%d",
                pthread_self(),conn->_fd,conn,_epoll.Fd());
                // 2.绑定自身epoll的绑法!!!
                BindConsumer(conn);
                // 3.添加到自身的epoll中
                AddConnection(conn);
            }
            LoopOnce(timeout);

        }
    }
    // 事件派发器 -- 生产者专属
    void Dispatcher()
    {
        // 能进行accept吗？
        // 不能，需要让epoll通知我们事件就绪
        int timeout = 1000; // 单位是毫秒
        while (true)
        {
            // Dispatcher：LoopOnce 只有listensock
            // 只负责对listensock的处理
            logMessage(Info, "%d-生产者#正在检测listsock-%d上的事件,我的epoll模型fd-%d",pthread_self(),_listensock.Fd(),_epoll.Fd());
            LoopOnce(timeout);
        }
    }
    // epoll 执行一次：
    void LoopOnce(int timeout)
    {
        // epoll_wait的返回值是 就绪事件的个数
        int n = _epoll.Wait(_revs, gnums, timeout);
        // 现在我们只关心合法的事件
        for (int i = 0; i < n; i++)
        {
            int fd = _revs[i].data.fd;
            uint32_t events = _revs[i].events;
            Connection *conn = _connections[fd];
            // 在处理对应的事件时，要保证对应的conn是存在的。
            // 因为都是并发的，并不是else的，前2个if中，有一个连接出现异常，
            // 异常的处理情况是释放掉相关结构，此时的conn是个非法的。再往下执行
            // 一定会报错的。

            logMessage(Debug, "当前正在处理%d上的%s", fd, (events & EPOLLIN) ? "EPOLLIN" : "OTHER");
            // 将异常情况归类到读写异常中去处理
            if (((events & EPOLLERR) || (events & EPOLLHUP)))
                events |= EPOLLIN | EPOLLOUT;
            if (events == EPOLLIN && IsExitConnction(fd))
                conn->_recver(conn);
            if (events == EPOLLOUT && IsExitConnction(fd))
                conn->_sender(conn);
        }
    }
    // 判断是否在连接集合中
    bool IsExitConnction(int fd)
    {
        return (_connections.find(fd) != _connections.end());
    }
    // 连接器
    void Accepter(Connection *conn)
    {
        // 在ET模式下，并不能保证同一时刻的连接只有一个，可能有很多个连接，也就是说你一次读取是会遗漏的
        // 必须循环读取
        do
        {
            std::string clientip;
            uint16_t clientport;
            int sock = _listensock.Accept(clientip, clientport);
            if (sock < 0)
            {
                if (errno == EAGAIN || errno == EWOULDBLOCK) // accept了，但是你没有数据了，换而言之已经accept完了
                    break;
                else if (errno == EINTR) // accept被信号中断了，重新accept
                    continue;
                else // 这一次的sock是真的出错了，下一次可能会正确
                    continue;
                logMessage(Warning, "accept error, code %d ,errstring %s", errno, strerror(errno));
            }
            else
            {
                // 引入生产者消费者模型后，生产不再关心 sock，而是将这个sock放入任务队列内部，
                // 让消费者去关心
                // AddConnection(sock, EPOLLIN, clientip, clientport);
                // 将sock放入任务队列中
                // 方便起见直接将Connection 加入任务队列
                Connection *conn = CreateConnection(sock, EPOLLET | EPOLLIN, clientip, clientport);
                logMessage(Info, "%d--生产者# 我获取到新连接sock %d,conn %p,并放入任务队列中", pthread_self(), sock,conn);
                /*
                    大BUG：到来了新的sock，不能进行绑定，因为绑定的是生产者的epoll，交给消费者后，调用的就是生产者的epoll
                    *************
                */
                _cq->push(conn);
            }
        } while (conn->_events & EPOLLET);
        logMessage(Debug, "accept success...");
    }
    // 创建一个Connection
    void BindProducer(Connection* conn)
    {
        conn->Register(std::bind(&EpollServer::Accepter, this, std::placeholders::_1), nullptr, nullptr);
        conn->_thisEpoll = this;
    }
    void BindConsumer(Connection* conn)
    {
        conn->Register(
            std::bind(&EpollServer::Recver, this, std::placeholders::_1),
            std::bind(&EpollServer::Sender, this, std::placeholders::_1),
            std::bind(&EpollServer::Excepter, this, std::placeholders::_1)
        );
        conn->_thisEpoll = this;
    }
    Connection *CreateConnection(int fd, int events, std::string ip = "127.0.0.1", uint16_t port = defaultport)
    {
        // 1.将对应的文件描述符设置为非阻塞
        Util::SetNoBlock(fd);
        // 2.创建集合对象
        Connection *conn = new Connection(fd, events, ip, port);
        // 绑定事件处理的方法

        // // 3.类内成员函数，被外部回调，需要使用bind进行绑定，因为有this指针
        // if (fd == _listensock.Fd())
        //     conn->Register(std::bind(&EpollServer::Accepter, this, std::placeholders::_1), nullptr, nullptr);
        // else
        // {
        //     conn->Register(
        //         std::bind(&EpollServer::Recver, this, std::placeholders::_1),
        //         std::bind(&EpollServer::Sender, this, std::placeholders::_1),
        //         std::bind(&EpollServer::Excepter, this, std::placeholders::_1));
        // }
        // 4.添加回指指针
        // conn->_thisEpoll = this;
        return conn;
    }
    // 将新的连接添加连接集合中
    void AddConnection(Connection *conn)
    {
        // 功能裁剪一下
        // // 1.将对应的文件描述符设置为非阻塞
        // Util::SetNoBlock(fd);
        // // 2.创建集合对象
        // Connection *conn = new Connection(fd, events, ip, port);
        // // 绑定事件处理的方法
        // // 3.类内成员函数，被外部回调，需要使用bind进行绑定，因为有this指针
        // if (fd == _listensock.Fd())
        //     conn->Register(std::bind(&EpollServer::Accepter, this, std::placeholders::_1), nullptr, nullptr);
        // else
        // {
        //     conn->Register(
        //         std::bind(&EpollServer::Recver, this, std::placeholders::_1),
        //         std::bind(&EpollServer::Sender, this, std::placeholders::_1),
        //         std::bind(&EpollServer::Excepter, this, std::placeholders::_1));
        // }
        // // 4.添加回指指针
        // conn->_thisEpoll = this;
        // 5.建立fd-connection的映射
        //调用CreateConnecition在哪里
        // 1. 加入connecntion集合中
        _connections.insert(std::pair<int, Connection *>(conn->_fd, conn));
        // 2. 让epoll关心
        _epoll.AddModEvent(EPOLL_CTL_ADD, conn->_fd, conn->_events);
    }

    // 读写开关
    void EnableReadWrite(Connection *conn, bool readable, bool writeable)
    {
        conn->_events = EPOLLET | (readable ? EPOLLIN : 0) | (writeable ? EPOLLOUT : 0);
        _epoll.AddModEvent(EPOLL_CTL_MOD, conn->_fd, conn->_events);
    }

    void HandlerRequest(Connection *conn)
    {
        // 3.3 将一次读取完的数据，进行解析
        while (true)
        {

            // 1. 获取完整报文
            std::string requestStr; // 请求
            int n = getPackage(conn->_inbuf, &requestStr);
            if (n <= 0) // 未解析到一个完整的报文
                break;
            // 2. 去掉报头
            requestStr = removeHeaders(requestStr, n);
            // 3. 反序列化
            Request request;
            request.Deserialize(requestStr);
            // 4. 业务处理
            // 这里的业务处理方案是，返回一个string，在epoll里进行序列化
            // 5. 获得响应
            Response res = _func(request);
            std::string resStr;
            // 6. 将响应序列化
            res.Serialize(&resStr);
            // 7. 添加报头
            resStr = addHeaders(resStr);
            // 8. 加入输出缓冲区中
            conn->_outbuf += resStr;
        }
    }
    bool RecverHelper(Connection *conn)
    {
        do
        {
            char buf[1024];
            int n = recv(conn->_fd, buf, sizeof(buf) - 1, 0);
            if (n > 0)
            {
                buf[n] = 0;
                conn->_inbuf += buf;
                std::cout << "_inbuf: " << conn->_inbuf;
            }
            else if (n == 0) // 对端关闭，当作异常处理
            {
                logMessage(Info, "peer is close, sock = %d", conn->_fd);
                conn->_excepter(conn);
                return false;
            }
            else
            {
                logMessage(Warning, "recv error, code %d ,errstring %s", errno, strerror(errno));
                // 读取了，但是没有数据
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR) // 这一次读取被信号中断了
                    continue;
                else // 这一次是真读取错了
                {
                    conn->_excepter(conn);
                    return false;
                }
            }
        } while (conn->_events & EPOLLET);

        return true;
    }
    // 读
    void Recver(Connection *conn)
    {
        logMessage(Info,"Recver start..");
        // 同理，当epoll是ET模式后，要保证一次读取完所有的数据，得是循环读取，并且要把读取的数据
        // 放到对应fd的缓冲区内部。 一直读，一直读，最后会把fd对应的缓冲区内部的数据读完
        if (!RecverHelper(conn)) // 出错了就返回
            return;
        HandlerRequest(conn);
        // 我们这里不在设置什么读写开关，我们有数据，我自己发送了，
        // 没有发送完的交给epoll帮我发送
        logMessage(Info,"HandlerRequest success\n");
        if (!conn->_outbuf.empty())
        {
            conn->_sender(conn);
            // if (!conn->_outbuf.empty()) // 第一次发送后没有发送完，那么我再把写开关打开
            //     EnableReadWrite(conn, true, true);
            // 放到sender中处理，并且当为空时，你还需要把开关关了
        }
    }
    // 发送
    void Sender(Connection *conn)
    {
        logMessage(Info, "send task start...");
        // epoll在ET模式下，只会通知一次，写事件就绪，对方的缓冲区中可以放数据
        // 那我要一次性，尽可能把对方的缓冲区打满，我要尽量把我的所有数据都发送过去
        do
        {
            logMessage(Info,"send before fd-%d,_outbuf-%s",conn->_fd,conn->_outbuf.c_str());            
            int n = send(conn->_fd, conn->_outbuf.c_str(), conn->_outbuf.size(), 0);
            if (n > 0)
            {
                // 发送成功,要把_outbuf中发送的部分移除
                conn->_outbuf.erase(0, n);
                std::cout << "_outbuf: " << conn->_outbuf;
                // 在写的时候我们写事件 epoll是没有关心的。
                // 在epoll中 读事件是常设置，但是写事件是按需设置
                // 因此我们需要一个函数，去控制读写事件 是否被打开.
                logMessage(Info,"%d-thread_id,fd: %d,epoll-fd-%d",pthread_self(),conn->_fd,_epoll.Fd());
                if (conn->_outbuf.empty())
                {
                    EnableReadWrite(conn, true, false);
                    break;
                }
                else
                    EnableReadWrite(conn, true, true);
            }
            else if (n == 0)
            {
                // 当返回值为0时表示对端关闭
                conn->_excepter(conn);
                return;
            }
            else
            {
                logMessage(Warning, "send error, code %d ,errstring %s", errno, strerror(errno));

                // 发送了，但是对方的缓冲区已经满了
                if (errno == EAGAIN || errno == EWOULDBLOCK)
                    break;
                else if (errno == EINTR)
                    continue;
                else
                {
                    conn->_excepter(conn);
                    return;
                }
            }
        } while (conn->_events & EPOLLET);
        logMessage(Info, "send task success...");

        if(!conn->_outbuf.empty()) // 第一次发送后没有发送完，那么我再把写开关打开
            EnableReadWrite(conn,true,true);
        else
            EnableReadWrite(conn,true,false);
    }
    // 异常处理
    void Excepter(Connection *conn)
    {
        logMessage(Info,"Excepter start...");
        // epoll删除fd
        _epoll.DelEvent(conn->_fd);
        // 关闭对应的文件描述符
        close(conn->_fd);
        // 删除对应的键值对
        _connections.erase(conn->_fd);
        // 释放Connection
        delete conn;
    }

private:
    Sock _listensock;
    uint16_t _port;
    Epoller _epoll;//你这里只有一个epoll模型，
    // epoll模型中，当有事件就绪时，通过revs告知用户
    struct epoll_event _revs[gnums];
    func_t _func;
    std::unordered_map<int, Connection *> _connections;
    // 多线程下看到同一份资源 -- 任务队列-->循环队列
    // _cq是临界资源
    // _cq内部已经是线程安全的了
    circularQueue<Connection *> *_cq;
};